{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\n以下代码将实现以下功能\\n\\n1. 迁移权重模型 MobileNet-v2,并改造为单支网络\\n2. 定义Siamese双支网络欧式距离\\n3. 根据距离大小定义样本对是否匹配\\n4. 定义输入样本对生成器（具体代码实现参考 from utils.images_generator import generator）\\n5. 通过model.fit_generator训练模型，保存模型\\n'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'''\n",
    "以下代码将实现以下功能\n",
    "\n",
    "1. 迁移权重模型 MobileNet-v2,并改造为单支网络\n",
    "2. 定义Siamese双支网络欧式距离\n",
    "3. 根据距离大小定义样本对是否匹配\n",
    "4. 定义输入样本对生成器（具体代码实现参考 from utils.images_generator import generator）\n",
    "5. 通过model.fit_generator训练模型，保存模型\n",
    "'''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import cv2\n",
    "import csv\n",
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import random\n",
    "from keras import backend as K\n",
    "from keras.preprocessing.image import img_to_array\n",
    "from keras.optimizers import SGD,RMSprop\n",
    "from keras.models import Model, Sequential,load_model\n",
    "from keras.layers import Input,Concatenate, Add,Subtract,Lambda,GlobalAveragePooling2D,Dense,Dropout\n",
    "from keras.callbacks import EarlyStopping, ModelCheckpoint\n",
    "from keras.optimizers import Adam,RMSprop,SGD"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#set GPU to memory-growth mode\n",
    "\n",
    "gpus = tf.config.experimental.list_physical_devices(device_type='GPU')\n",
    "for gpu in gpus:\n",
    "    tf.config.experimental.set_memory_growth(gpu, True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# calculate euclidean distance\n",
    "def euclidean_distance(vects):\n",
    "    x, y = vects\n",
    "    sum_square = K.sum(K.square(x - y), axis=1, keepdims=True)\n",
    "    return K.sqrt(K.maximum(sum_square, K.epsilon()))\n",
    "\n",
    "\n",
    "def eucl_dist_output_shape(shapes):\n",
    "    shape1, shape2 = shapes\n",
    "    return (shape1[0], 1)\n",
    "\n",
    "\n",
    "# calculate loss\n",
    "def contrastive_loss(y_true, y_pred):\n",
    "    margin = 1\n",
    "    square_pred = K.square(y_pred)\n",
    "    margin_square = K.square(K.maximum(margin - y_pred, 0))\n",
    "    return K.mean(y_true * square_pred + (1 - y_true) * margin_square)\n",
    "\n",
    "# calculate accuracy\n",
    "def compute_accuracy(y_true, y_pred):\n",
    "    pred = y_pred.ravel() < 0.5\n",
    "    print('pred:', pred)\n",
    "    return np.mean(pred == y_true)\n",
    "\n",
    "\n",
    "def accuracy(y_true, y_pred):\n",
    "    '''Compute classification accuracy with a fixed threshold on distances.\n",
    "    '''\n",
    "    return K.mean(K.equal(y_true, K.cast(y_pred < 0.5, y_true.dtype)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model_1\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_2 (InputLayer)         (None, 224, 224, 3)       0         \n",
      "_________________________________________________________________\n",
      "mobilenetv2_1.00_None (Model multiple                  2257984   \n",
      "_________________________________________________________________\n",
      "global_average_pooling2d_1 ( (None, 1280)              0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 128)               163968    \n",
      "_________________________________________________________________\n",
      "dropout_1 (Dropout)          (None, 128)               0         \n",
      "_________________________________________________________________\n",
      "dense_2 (Dense)              (None, 50)                6450      \n",
      "=================================================================\n",
      "Total params: 2,428,402\n",
      "Trainable params: 170,418\n",
      "Non-trainable params: 2,257,984\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "from keras.applications import ResNet50,MobileNetV2,DenseNet121\n",
    "from models.base_model import base_model\n",
    "\n",
    "\n",
    "weights_path='/root/tf-2.0/Siamese_Face_Detection/models/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5'\n",
    "initial_model=MobileNetV2(weights=weights_path,include_top=False)\n",
    "\n",
    "\n",
    "Inp=Input((224,224,3))\n",
    "x=initial_model(Inp)\n",
    "x=GlobalAveragePooling2D()(x)\n",
    "x=Dense(128,activation='relu')(x)\n",
    "x=Dropout(0.1)(x)\n",
    "pred=Dense(50,activation='relu')(x)\n",
    "\n",
    "base_network=Model(inputs=Inp,outputs=pred)\n",
    "base_network.layers[0].trainable = False\n",
    "base_network.layers[1].trainable = False\n",
    "\n",
    "base_network.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model_2\"\n",
      "__________________________________________________________________________________________________\n",
      "Layer (type)                    Output Shape         Param #     Connected to                     \n",
      "==================================================================================================\n",
      "input_3 (InputLayer)            (None, 224, 224, 3)  0                                            \n",
      "__________________________________________________________________________________________________\n",
      "input_4 (InputLayer)            (None, 224, 224, 3)  0                                            \n",
      "__________________________________________________________________________________________________\n",
      "model_1 (Model)                 (None, 50)           2428402     input_3[0][0]                    \n",
      "                                                                 input_4[0][0]                    \n",
      "__________________________________________________________________________________________________\n",
      "lambda_1 (Lambda)               (None, 1)            0           model_1[1][0]                    \n",
      "                                                                 model_1[2][0]                    \n",
      "==================================================================================================\n",
      "Total params: 2,428,402\n",
      "Trainable params: 170,418\n",
      "Non-trainable params: 2,257,984\n",
      "__________________________________________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "input_shape=(224,224,3)\n",
    "input_a = Input(shape=input_shape)\n",
    "input_b = Input(shape=input_shape)\n",
    "\n",
    "# because we re-use the same instance `base_network`,\n",
    "# the weights of the network\n",
    "# will be shared across the two branches\n",
    "processed_a = base_network(input_a)\n",
    "processed_b = base_network(input_b)\n",
    "\n",
    "distance = Lambda(euclidean_distance,\n",
    "                  output_shape=eucl_dist_output_shape)([processed_a, processed_b])\n",
    "\n",
    "model = Model([input_a, input_b], distance)\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#download training images pair\n",
    "train_csv_path='/root/tf-2.0/Siamese_Face_Detection/data/FaceV5_160_train.csv'\n",
    "test_csv_path='/root/tf-2.0/Siamese_Face_Detection/data/FaceV5_160_test.csv'\n",
    "\n",
    "rows_train = csv.reader(open(train_csv_path, 'r'), delimiter=',')\n",
    "imgs_train = list(rows_train)\n",
    "\n",
    "rows_test=csv.reader(open(test_csv_path, 'r'), delimiter=',')\n",
    "imgs_test = list(rows_test)\n",
    "\n",
    "#define iterations\n",
    "epochs = 5\n",
    "batch_size = 20\n",
    "train_iterations = len(imgs_train)//20\n",
    "test_iterations = len(imgs_test)//20\n",
    "\n",
    "#choose the proper optimizer\n",
    "initial_lr=0.001\n",
    "decay_rate=initial_lr/epochs\n",
    "sgd=SGD(learning_rate=initial_lr,decay=decay_rate,momentum=0.9)\n",
    "rms=RMSprop()\n",
    "adam = Adam(0.0001)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/5\n",
      "3000/3000 [==============================] - 224s 75ms/step - loss: 0.0979 - accuracy: 0.9231 - val_loss: 0.0839 - val_accuracy: 0.9318\n",
      "\n",
      "Epoch 00001: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_mobilenetv2_001.h5\n",
      "Epoch 2/5\n",
      "3000/3000 [==============================] - 217s 72ms/step - loss: 0.0812 - accuracy: 0.9250 - val_loss: 0.0518 - val_accuracy: 0.9169\n",
      "\n",
      "Epoch 00002: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_mobilenetv2_002.h5\n",
      "Epoch 3/5\n",
      "3000/3000 [==============================] - 217s 72ms/step - loss: 0.0763 - accuracy: 0.9236 - val_loss: 0.0800 - val_accuracy: 0.9110\n",
      "\n",
      "Epoch 00003: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_mobilenetv2_003.h5\n",
      "Epoch 4/5\n",
      "3000/3000 [==============================] - 216s 72ms/step - loss: 0.0718 - accuracy: 0.9242 - val_loss: 0.1118 - val_accuracy: 0.8740\n",
      "\n",
      "Epoch 00004: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_mobilenetv2_004.h5\n",
      "Epoch 5/5\n",
      "3000/3000 [==============================] - 218s 73ms/step - loss: 0.0682 - accuracy: 0.9264 - val_loss: 0.0751 - val_accuracy: 0.8899\n",
      "\n",
      "Epoch 00005: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_mobilenetv2_005.h5\n"
     ]
    }
   ],
   "source": [
    "from utils.images_generator import generator\n",
    "\n",
    "model_result = '/root/tf-2.0/Siamese_Face_Detection/h5/'\n",
    "#define training iterator\n",
    "train_data=generator(imgs_train, batch_size,input_shape)\n",
    "valid_data=validation_data = generator(imgs_test,batch_size,input_shape)\n",
    "\n",
    "checkpoint = ModelCheckpoint(filepath=model_result + 'Siamese_mobilenetv2_{epoch:03d}.h5', verbose=1)\n",
    "model.compile(loss=contrastive_loss, optimizer=adam, metrics=[accuracy])\n",
    "history=model.fit_generator(train_data,\n",
    "                            steps_per_epoch = train_iterations,\n",
    "                            epochs = epochs,\n",
    "                            validation_data = valid_data,\n",
    "                            validation_steps = test_iterations,\n",
    "                            callbacks=[checkpoint])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
